package service

import (
	"batbat-backend/app/dao"
	"batbat-backend/app/model"
	"context"
	"fmt"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/util/gconv"
)

// Recommend 用户推荐服务
var Recommend = recommendService{}

const changeSetKey = "user:category:change"

type recommendService struct{}

// Record 记录用户读取一次游戏种类
func (r *recommendService) Record(ctx context.Context, uid uint, categories []*model.Category) {
	if categories == nil || len(categories) == 0 {
		return
	}
	// 获取key
	key := fmt.Sprintf("user:%d:category", uid)
	// 获取redis对象
	redis := g.Redis().Ctx(ctx)
	// 用户对应的种类自增
	for _, c := range categories {
		_, err := redis.Do("HINCRBY", key, c.Id, 1)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("redis错误: %v", err)
			break
		}
	}
	// 将用户添加进变化集合
	_, err := redis.Do("SADD", changeSetKey, uid)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("redis错误: %v", err)
	}
}

// SaveRecord 保存用户读取游戏种类的次数
func (r *recommendService) SaveRecord() {
	var (
		ctx   = context.Background() // 本次执行的上下文变量
		redis = g.Redis().Ctx(ctx)   // redis操作实例
	)
	// 获取这段时间内有修改的用户
	vuids, err := redis.DoVar("SMEMBERS", changeSetKey)
	if err != nil {
		g.Log().Ctx(ctx).Errorf("redis错误: %v", err)
		return
	}
	// 遍历有修改的用户, 获取查看内容并写回数据库
	for _, suid := range vuids.Array() {
		uid := gconv.Uint(suid)
		if uid == 0 {
			continue
		}
		key := fmt.Sprintf("user:%d:category", uid)
		vrecords, err := redis.DoVar("HGETALL", key)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("redis错误: %v", err)
			continue
		}
		var records map[uint]uint
		err = vrecords.Scan(&records)
		if err != nil {
			g.Log().Ctx(ctx).Errorf("gconv失败: %v", err)
			continue
		}
		// 保存用户的浏览种类信息
		err = g.DB().Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
			var (
				tucs = make([]*model.UserCategory, 0)    // 在保存前从数据库获取的内容
				aucs = make([]*model.UserCategory, 0)    // 需要写回数据库的内容
				bucs = make(map[uint]uint, 0)            // 需要写回redis的内容
				t    = make(map[uint]bool, len(records)) // 记录在数据库中是否有对应的项
			)
			// 初始化keys与t
			for key := range records {
				t[key] = false
			}
			// 获取数据库中存储的用户的所有浏览种类信息
			err := dao.UserCategory.Ctx(ctx).TX(tx).LockUpdate().
				Where(dao.UserCategory.Columns.Uid, uid).Scan(&tucs)
			if err != nil {
				return err
			}
			for _, tuc := range tucs {
				t[tuc.Cid] = true
				record, ok := records[tuc.Cid]
				if !ok {
					// 该内容需要写入redis
					bucs[tuc.Cid] = tuc.Frequency
					continue
				}
				if tuc.Frequency > record {
					// 该内容需要写回redis
					bucs[tuc.Cid] = tuc.Frequency
				} else if tuc.Frequency < record {
					// 该内容需要写入数据库
					aucs = append(aucs, &model.UserCategory{
						Uid:        uid,
						Cid:        tuc.Cid,
						UpdateTime: gtime.Now(),
						Frequency:  record,
					})
				}
			}
			for c, b := range t {
				if !b {
					// 同样需要写回数据库的内容
					aucs = append(aucs, &model.UserCategory{
						Uid:        uid,
						Cid:        c,
						UpdateTime: gtime.Now(),
						Frequency:  records[c],
					})
				}
			}
			// 写入数据库
			if len(aucs) != 0 {
				_, err = dao.UserCategory.Ctx(ctx).TX(tx).
					FieldsEx(dao.UserCategory.Columns.Id, dao.UserCategory.Columns.CreateTime).
					Batch(100).
					Save(aucs)
				if err != nil {
					return err
				}
			}
			// 写回redis
			for cid, f := range bucs {
				_, err := redis.Do("HSET", key, cid, f)
				if err != nil {
					return err
				}
			}
			// 删除该修改用户
			_, err = redis.Do("SREM", changeSetKey, uid)
			return err
		})
		if err != nil {
			g.Log().Ctx(ctx).Errorf("保存用户[%d]记录失败: %v", uid, err)
			continue
		}
	}
}
